/*
 * Copyright (c) 2020-2021, SERI Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-05-22     Lyons        first version
 */

module pa_fpu_xfrac_add_sub (
    clk_i,
    rst_n_i,

    src0_i,
    src1_i,

    single_i,
    double_i,

    sel_unit_i,
    sel_func_i,

    type_normal_i,
    type_subnormal_i,

    rm_i,

    ex1_clk_i,
    ex1_pipedown_i,
    ex1_cancel_i,
    ex2_clk_i,
    ex2_pipedown_i,
    ex2_cancel_i,
    ex3_clk_i,
    ex3_pipedown_i,
    ex3_cancel_i,

    fresult_o,
    fresult_vld_o
    );

`include "pa_fpu_param.v"

input                           clk_i;
input                           rst_n_i;

input                           src0_i;
input                           src1_i;

input                           single_i;
input                           double_i;

input                           sel_unit_i;
input                           sel_func_i;

input                           type_normal_i;
input                           type_subnormal_i;

input                           rm_i;

input                           ex1_clk_i;
input                           ex1_pipedown_i;
input                           ex1_cancel_i;
input                           ex2_clk_i;
input                           ex2_pipedown_i;
input                           ex2_cancel_i;
input                           ex3_clk_i;
input                           ex3_pipedown_i;
input                           ex3_cancel_i;

output                          fresult_o;
output                          fresult_vld_o;

wire                            clk_i;
wire                            rst_n_i;

wire [`FLOAT_WIDTH-1:0]         src0_i;
wire [`FLOAT_WIDTH-1:0]         src1_i;

wire                            single_i;
wire                            double_i;

wire [2:0]                      sel_unit_i;
wire [9:0]                      sel_func_i;

wire [2:0]                      type_normal_i;
wire [2:0]                      type_subnormal_i;

wire [2:0]                      rm_i;

wire                            ex1_clk_i;
wire                            ex1_pipedown_i;
wire                            ex1_cancel_i;
wire                            ex2_clk_i;
wire                            ex2_pipedown_i;
wire                            ex2_cancel_i;
wire                            ex3_clk_i;
wire                            ex3_pipedown_i;
wire                            ex3_cancel_i;

wire [`FLOAT_WIDTH-1:0]         fresult_o;
wire                            fresult_vld_o;


wire                            ex1_single_fmt;

wire [`FLOAT_WIDTH-1:0]         ex1_src0;
wire [`FLOAT_WIDTH-1:0]         ex1_src1;

// if single_i and double_i are both 1, do as double.
assign ex1_single_fmt = single_i & ~double_i;

assign ex1_src0[`FLOAT_WIDTH-1:0] = src0_i[`FLOAT_WIDTH-1:0];
assign ex1_src1[`FLOAT_WIDTH-1:0] = src1_i[`FLOAT_WIDTH-1:0];

wire                            ex1_src0_sign;
wire [`EXPN_WIDTH-1:0]          ex1_src0_expn_raw;
wire [`FRAC_WIDTH-1:0]          ex1_src0_frac;

wire                            ex1_src1_sign;
wire [`EXPN_WIDTH-1:0]          ex1_src1_expn_raw;
wire [`FRAC_WIDTH-1:0]          ex1_src1_frac;

pa_fpu_data_unpack u_pa_fpu_data_unpack_src0 (
    .single_i                   (ex1_single_fmt),
    .src_i                      (ex1_src0),
    .rst_sign_o                 (ex1_src0_sign),
    .rst_expn_o                 (ex1_src0_expn_raw),
    .rst_frac_o                 (ex1_src0_frac)
);

pa_fpu_data_unpack u_pa_fpu_data_unpack_src1 (
    .single_i                   (ex1_single_fmt),
    .src_i                      (ex1_src1),
    .rst_sign_o                 (ex1_src1_sign),
    .rst_expn_o                 (ex1_src1_expn_raw),
    .rst_frac_o                 (ex1_src1_frac)
);

wire                            ex1_op_vld;

wire                            ex1_op_sub;
wire                            ex1_op_sel;
wire                            ex1_op_cmp;
wire                            ex1_op_cmp_sub;
wire                            ex1_op_cmp_sel;

// this flag is equal to pipedown, used to indicate falu working normally
assign ex1_op_vld =  ( sel_unit_i[2:0] == `SEL_UNIT_FALU)
                 &&  (&type_normal_i[1:0])
                 && ~(|sel_func_i[6:3]);

// if sel_unit_i[2:0] indicats falu
//  9       8       7       6       5       4       3       [2   1   0]
//  sub/add sel     cmp     mv      sgnj    cnvt    class   append-func

assign ex1_op_sub = ex1_op_vld & sel_func_i[9]; //only valid with falu selected
assign ex1_op_sel = ex1_op_vld & sel_func_i[8];
assign ex1_op_cmp = ex1_op_vld & sel_func_i[7];

assign ex1_op_cmp_sub = ex1_op_sub | ex1_op_cmp | ex1_op_sel;
assign ex1_op_cmp_sel = ex1_op_sel; //only for max and min

wire [`DOUBLE_EXPN_WIDTH:0]     ex1_src0_expn;
wire [`DOUBLE_EXPN_WIDTH:0]     ex1_src1_expn;

wire [`DOUBLE_FRAC_WIDTH:0]     ex1_src0_xfrac;
wire [`DOUBLE_FRAC_WIDTH:0]     ex1_src1_xfrac;

// total 12-bits = overflow:1 + expn:11
assign ex1_src0_expn[`DOUBLE_EXPN_WIDTH:0] = {1'b0, ex1_src0_expn_raw[`DOUBLE_EXPN_WIDTH-1:0]}; //add overflow bit
assign ex1_src1_expn[`DOUBLE_EXPN_WIDTH:0] = {1'b0, ex1_src1_expn_raw[`DOUBLE_EXPN_WIDTH-1:0]};

// total 53-bits = hidden:1 + frac:52
assign ex1_src0_xfrac[`DOUBLE_FRAC_WIDTH:0] = {~type_subnormal_i[0], ex1_src0_frac[`DOUBLE_FRAC_WIDTH-1:0]}; //add hidden interger bit
assign ex1_src1_xfrac[`DOUBLE_FRAC_WIDTH:0] = {~type_subnormal_i[1], ex1_src1_frac[`DOUBLE_FRAC_WIDTH-1:0]};

wire [`DOUBLE_EXPN_WIDTH:0]     ex1_expn_diff_0_1;
wire [`DOUBLE_EXPN_WIDTH:0]     ex1_expn_diff_1_0;
wire [`DOUBLE_EXPN_WIDTH:0]     ex1_expn_diff;
wire                            ex1_expn_diff_less2;
wire [`DOUBLE_EXPN_WIDTH:0]     ex1_expn_big;
wire                            ex1_expn_big_idx;

assign ex1_expn_diff_0_1[`DOUBLE_EXPN_WIDTH:0] = ex1_src0_expn[`DOUBLE_EXPN_WIDTH:0] - ex1_src1_expn[`DOUBLE_EXPN_WIDTH:0];
assign ex1_expn_diff_1_0[`DOUBLE_EXPN_WIDTH:0] = ex1_src1_expn[`DOUBLE_EXPN_WIDTH:0] - ex1_src0_expn[`DOUBLE_EXPN_WIDTH:0];

assign ex1_expn_big_idx = ex1_expn_diff_0_1[`DOUBLE_EXPN_WIDTH];

assign ex1_expn_big[`DOUBLE_EXPN_WIDTH:0] = ex1_expn_big_idx ? ex1_src1_expn[`DOUBLE_EXPN_WIDTH:0]
                                                             : ex1_src0_expn[`DOUBLE_EXPN_WIDTH:0];

// ex1_expn_diff choose positive value
assign ex1_expn_diff[`DOUBLE_EXPN_WIDTH:0] = ex1_expn_big_idx ? ex1_expn_diff_1_0[`DOUBLE_EXPN_WIDTH:0]
                                                              : ex1_expn_diff_0_1[`DOUBLE_EXPN_WIDTH:0];

assign ex1_expn_diff_less2 = ex1_expn_diff[`DOUBLE_EXPN_WIDTH-1:0] < 11'b10;

wire                            ex1_sign_big;

assign ex1_sign_big = ex1_expn_big_idx ? ex1_src1_sign
                                       : ex1_src0_sign;

wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_a_raw;
wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_a_nor;
wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_b_raw;
wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_b_shifted;
wire [2:0]                      ex1_xfrac_b_grs;

assign ex1_xfrac_a_raw[`DOUBLE_FRAC_WIDTH:0] = ex1_expn_big_idx ? ex1_src1_xfrac[`DOUBLE_FRAC_WIDTH:0]
                                                                : ex1_src0_xfrac[`DOUBLE_FRAC_WIDTH:0];

assign ex1_xfrac_a_nor[`DOUBLE_FRAC_WIDTH:0] = ~ex1_xfrac_a_raw[`DOUBLE_FRAC_WIDTH:0];

assign ex1_xfrac_b_raw[`DOUBLE_FRAC_WIDTH:0] = ex1_expn_big_idx ? ex1_src0_xfrac[`DOUBLE_FRAC_WIDTH:0]
                                                                : ex1_src1_xfrac[`DOUBLE_FRAC_WIDTH:0];

pa_fpu_xfrac_pre_shift u_pa_fpu_xfrac_pre_shift (
    .expn_diff_i                (ex1_expn_diff[10:0]),
    .xfrac_i                    (ex1_xfrac_b_raw),
    .xfrac_o                    (ex1_xfrac_b_shifted), //53-bits
    .grs_o                      (ex1_xfrac_b_grs)
);

wire                            ex1_op_act_sign;
wire                            ex1_op_act_sub;

assign ex1_op_act_sign = (ex1_expn_big_idx && ex1_op_cmp_sub) ? ~ex1_sign_big
                                                              :  ex1_sign_big;

assign ex1_op_act_sub  = ~ex1_op_cmp_sub & (ex1_src0_sign ^  ex1_src1_sign) //add
                       |  ex1_op_cmp_sub & (ex1_src0_sign ^ ~ex1_src1_sign); //sub

wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_a;
wire [`DOUBLE_FRAC_WIDTH:0]     ex1_xfrac_b;

// a is the biggest while b is smaller
assign ex1_xfrac_a[`DOUBLE_FRAC_WIDTH:0] = ex1_op_act_sub ? ex1_xfrac_a_nor[`DOUBLE_FRAC_WIDTH:0]
                                                          : ex1_xfrac_a_raw[`DOUBLE_FRAC_WIDTH:0];

assign ex1_xfrac_b[`DOUBLE_FRAC_WIDTH:0] = ex1_xfrac_b_shifted[`DOUBLE_FRAC_WIDTH:0];

wire                            ex1_expn_no_limit;

wire [2:0]                      ex1_expn_limit_high;
wire [2:0]                      ex1_expn_limit_low;

assign ex1_expn_no_limit = |ex1_expn_big[`DOUBLE_EXPN_WIDTH:6]; //over 63

assign ex1_expn_limit_high[2:0] = ex1_expn_big[5:3];
assign ex1_expn_limit_low[2:0]  = ex1_expn_big[2:0];

reg  [7:0]                      ex1_expn_mask_factor;
reg  [63:0]                     ex1_expn_mask_raw;

wire [63:0]                     ex1_expn_mask;

always @ (ex1_expn_limit_low[2:0]) begin
case (ex1_expn_limit_low[2:0])
    3'd0 : ex1_expn_mask_factor[7:0] = 8'b1000_0000;
    3'd1 : ex1_expn_mask_factor[7:0] = 8'b0100_0000;
    3'd2 : ex1_expn_mask_factor[7:0] = 8'b0010_0000;
    3'd3 : ex1_expn_mask_factor[7:0] = 8'b0001_0000;
    3'd4 : ex1_expn_mask_factor[7:0] = 8'b0000_1000;
    3'd5 : ex1_expn_mask_factor[7:0] = 8'b0000_0100;
    3'd6 : ex1_expn_mask_factor[7:0] = 8'b0000_0010;
    3'd7 : ex1_expn_mask_factor[7:0] = 8'b0000_0001;
endcase
end

always @ (ex1_expn_limit_high[2:0] or ex1_expn_mask_factor[7:0]) begin
case (ex1_expn_limit_high[2:0])
    3'd0 : ex1_expn_mask_raw[63:0] = {       ex1_expn_mask_factor[7:0], 56'b0};
    3'd1 : ex1_expn_mask_raw[63:0] = {8'b0,  ex1_expn_mask_factor[7:0], 48'b0};
    3'd2 : ex1_expn_mask_raw[63:0] = {16'b0, ex1_expn_mask_factor[7:0], 40'b0};
    3'd3 : ex1_expn_mask_raw[63:0] = {24'b0, ex1_expn_mask_factor[7:0], 32'b0};
    3'd4 : ex1_expn_mask_raw[63:0] = {32'b0, ex1_expn_mask_factor[7:0], 24'b0};
    3'd5 : ex1_expn_mask_raw[63:0] = {40'b0, ex1_expn_mask_factor[7:0], 16'b0};
    3'd6 : ex1_expn_mask_raw[63:0] = {48'b0, ex1_expn_mask_factor[7:0],  8'b0};
    3'd7 : ex1_expn_mask_raw[63:0] = {56'b0, ex1_expn_mask_factor[7:0]       };
endcase
end

reg                             ex2_expn_no_limit;

assign ex1_expn_mask[63:0] = {64{~ex2_expn_no_limit}} & ex1_expn_mask_raw[63:0];

reg  [`DOUBLE_FRAC_WIDTH:0]     ex2_xfrac_a;
reg  [`DOUBLE_FRAC_WIDTH:0]     ex2_xfrac_b;
reg  [2:0]                      ex2_xfrac_b_grs;
reg                             ex2_op_vld;
reg                             ex2_op_act_sub;
reg                             ex2_op_act_sign;
reg                             ex2_single_fmt;
reg  [2:0]                      ex2_rm;
reg  [`DOUBLE_EXPN_WIDTH:0]     ex2_expn;
reg                             ex2_expn_diff_less2;
reg  [63:0]                     ex2_expn_mask;

always @ (posedge ex1_clk_i) begin
    if (ex1_pipedown_i & ~ex1_cancel_i) begin
        ex2_xfrac_a[`DOUBLE_FRAC_WIDTH:0] <= ex1_xfrac_a[`DOUBLE_FRAC_WIDTH:0];
        ex2_xfrac_b[`DOUBLE_FRAC_WIDTH:0] <= ex1_xfrac_b[`DOUBLE_FRAC_WIDTH:0];
        ex2_xfrac_b_grs[2:0]              <= ex1_xfrac_b_grs[2:0];

        ex2_expn[`DOUBLE_EXPN_WIDTH:0]    <= ex1_expn_big[`DOUBLE_EXPN_WIDTH:0];
        ex2_expn_diff_less2               <= ex1_expn_diff_less2;
        ex2_expn_mask[63:0]               <= ex1_expn_mask[63:0];
        ex2_expn_no_limit                 <= ex1_expn_no_limit;

        ex2_op_vld                        <= ex1_op_vld;
        ex2_op_act_sub                    <= ex1_op_act_sub;
        ex2_op_act_sign                   <= ex1_op_act_sign;

        ex2_single_fmt                    <= ex1_single_fmt;
        ex2_rm[2:0]                       <= rm_i[2:0];
    end
    else begin
        ex2_xfrac_a[`DOUBLE_FRAC_WIDTH:0] <= 53'b0;
        ex2_xfrac_b[`DOUBLE_FRAC_WIDTH:0] <= 53'b0;
        ex2_xfrac_b_grs[2:0]              <= 3'b0;

        ex2_expn[`DOUBLE_EXPN_WIDTH:0]    <= 12'b0;
        ex2_expn_diff_less2               <= 1'b0;
        ex2_expn_mask[63:0]               <= 64'b0;
        ex2_expn_no_limit                 <= 1'b0;

        ex2_op_vld                        <= 1'b0;
        ex2_op_act_sub                    <= 1'b0;
        ex2_op_act_sign                   <= 1'b0;

        ex2_single_fmt                    <= 1'b0;
        ex2_rm[2:0]                       <= 3'b0;
    end
end

wire                            ex2_single;
wire                            ex2_double;

assign ex2_single =  ex1_single_fmt;
assign ex2_double = ~ex1_single_fmt;

wire [56:0]                     ex2_expn_mask_final;

assign ex2_expn_mask_final[56:0] = ex2_single ? {29'b0, ex2_expn_mask[63:38], 2'b0}
                                              : {       ex2_expn_mask[63:9],  2'b0}; //rs only valid with g valid

wire [56:0]                     ex2_frac_adder_src0;
wire [56:0]                     ex2_frac_adder_src1;
wire [56:0]                     ex2_frac_lop_src0;
wire [56:0]                     ex2_frac_lop_src1;
wire [56:0]                     ex2_frac_adder_rst0;
wire [56:0]                     ex2_frac_adder_rst0_nor;
wire [56:0]                     ex2_frac_adder_rst1;
wire                            ex2_frac_adder_rst_neg;
wire [56:0]                     ex2_frac_adder_sub_rst;
wire [56:0]                     ex2_frac_adder_add_rst;
wire [56:0]                     ex2_frac_adder_rst;

// total 57-bits = overflow:1 + xfrac:53 + grs:3
assign ex2_frac_adder_src0[56:0] = {ex2_op_act_sub, ex2_xfrac_a[`DOUBLE_FRAC_WIDTH:0], {3{ex2_op_act_sub}}};
assign ex2_frac_adder_src1[56:0] = {1'b0, ex2_xfrac_b[`DOUBLE_FRAC_WIDTH:0], ex2_xfrac_b_grs[2:0]};

assign ex2_frac_lop_src0[56:0] = {1'b0, ~ex2_xfrac_a[`DOUBLE_FRAC_WIDTH:0], 3'b0};
assign ex2_frac_lop_src1[56:0] = ex2_frac_adder_src1[56:0];

assign ex2_frac_adder_rst0[56:0]     = ex2_frac_adder_src0[56:0] + ex2_frac_adder_src1[56:0];
assign ex2_frac_adder_rst0_nor[56:0] = ~ex2_frac_adder_rst0[56:0];
assign ex2_frac_adder_rst1[56:0]     = ex2_frac_adder_src0[56:0] + ex2_frac_adder_src1[56:0] + 1'b1; //(~|src0|+1) + |src1|

assign ex2_frac_adder_rst_neg = ex2_frac_adder_rst1[56];

assign ex2_frac_adder_add_rst[56:0] = ex2_frac_adder_rst0[56:0];
assign ex2_frac_adder_sub_rst[56:0] = ex2_frac_adder_rst_neg ? ex2_frac_adder_rst0_nor[56:0]
                                                             : ex2_frac_adder_rst1[56:0];

// result is positive
assign ex2_frac_adder_rst[56:0] = ~ex2_op_act_sub ? ex2_frac_adder_add_rst[56:0]
                                                  : ex2_frac_adder_sub_rst[56:0];

wire [5:0]                      ex2_rst_expn_pred_single;
wire [5:0]                      ex2_rst_expn_pred_double;
wire [2:0]                      ex2_rst_frac_pred_low;
wire [3:0]                      ex2_rst_frac_pred_high;

pa_fpu_xfrac_pre_lop u_pa_fpu_xfrac_pre_lop (
    .expn_mask_i                (ex2_expn_mask_final),
    .xfrac_rnd_src0_i           (ex2_frac_lop_src0),
    .xfrac_rnd_src1_i           (ex2_frac_lop_src1),
    .expn_adjust_single_o       (ex2_rst_expn_pred_single),
    .expn_adjust_double_o       (ex2_rst_expn_pred_double),
    .xfrac_shift_low_o          (ex2_rst_frac_pred_low),
    .xfrac_shift_high_o         (ex2_rst_frac_pred_high)
);

wire [`DOUBLE_EXPN_WIDTH:0]     ex2_expn_adjust_diff;

// result is positive
assign ex2_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0] = {12{ex2_single}} & {6'b0, ex2_rst_expn_pred_single[5:0]}
                                                  | {12{ex2_double}} & {6'b0, ex2_rst_expn_pred_double[5:0]};

wire                            ex2_rst_act_sign;

assign ex2_rst_act_sign = (ex2_op_act_sub && !ex2_frac_adder_rst_neg) ? ~ex2_op_act_sign
                                                                      :  ex2_op_act_sign;

reg  [`DOUBLE_EXPN_WIDTH:0]     ex3_expn;
reg  [`DOUBLE_EXPN_WIDTH:0]     ex3_expn_adjust_diff;
reg  [56:0]                     ex3_frac_adder_rst;
reg  [2:0]                      ex3_rst_pred_low;
reg  [3:0]                      ex3_rst_pred_high;
reg                             ex3_op_vld;
reg                             ex3_op_act_sub;
reg                             ex3_op_act_sign;
reg                             ex3_single_fmt;
reg  [2:0]                      ex3_rm;

always @ (posedge ex2_clk_i) begin
    if (ex2_pipedown_i & ~ex2_cancel_i) begin
        ex3_frac_adder_rst[56:0]                   <= ex2_frac_adder_rst[56:0];

        ex3_expn[`DOUBLE_EXPN_WIDTH:0]             <= ex2_expn[`DOUBLE_EXPN_WIDTH:0];
        ex3_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0] <= ex2_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0];

        ex3_rst_pred_low[2:0]                      <= ex2_rst_frac_pred_low[2:0];
        ex3_rst_pred_high[3:0]                     <= ex2_rst_frac_pred_high[3:0];

        ex3_op_vld                                 <= ex2_op_vld;
        ex3_op_act_sub                             <= ex2_op_act_sub;
        ex3_op_act_sign                            <= ex2_rst_act_sign;

        ex3_single_fmt                             <= ex2_single_fmt;
        ex3_rm[2:0]                                <= ex2_rm[2:0];
    end
    else begin
        ex3_frac_adder_rst[56:0]                   <= 57'b0;

        ex3_expn[`DOUBLE_EXPN_WIDTH:0]             <= 12'b0;
        ex3_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0] <= 12'b0;

        ex3_rst_pred_low[2:0]                      <= 3'b0;
        ex3_rst_pred_high[3:0]                     <= 4'b0;

        ex3_op_vld                                 <= 1'b0;
        ex3_op_act_sub                             <= 1'b0;
        ex3_op_act_sign                            <= 1'b0;

        ex3_single_fmt                             <= 1'b0;
        ex3_rm[2:0]                                <= 3'b0;
    end
end

wire [56:0]                     ex3_frac_adder_rst_no_shift;
wire [56:0]                     ex3_frac_adder_rst_shift;
wire [56:0]                     ex3_frac_adder_rst_sh1;
wire [56:0]                     ex3_frac_adder_rst_sh2;

assign ex3_frac_adder_rst_no_shift[56:0] = ex3_frac_adder_rst[56:0];
assign ex3_frac_adder_rst_shift[56:0] = ex3_frac_adder_rst_sh2[56:0];

pa_fpu_xfrac_post_shift_left u_pa_fpu_xfrac_post_shift_left (
    .expn_adjust_i              (ex3_rst_pred_low),
    .xfrac_rnd_i                (ex3_frac_adder_rst_no_shift),
    .xfrac_rnd_o                (ex3_frac_adder_rst_sh1)
);

pa_fpu_xfrac_post_shift_left_h u_pa_fpu_xfrac_post_shift_left_h (
    .expn_adjust_i              (ex3_rst_pred_high),
    .xfrac_rnd_i                (ex3_frac_adder_rst_sh1),
    .xfrac_rnd_o                (ex3_frac_adder_rst_sh2) //sh2 is standard xfrac under sub
);

// overflow-bit, interger-bit, subinterger-bit, other-bit
wire                            ex3_add_1xxx;
wire                            ex3_add_01xx;

wire                            ex3_sub_01xx;
wire                            ex3_sub_001x;
wire                            ex3_sub_000x;

// expn +1
assign ex3_add_1xxx = ~ex3_op_act_sub
                    &  ex3_frac_adder_rst_no_shift[56];

// expn +0 or +1
assign ex3_add_01xx = ~ex3_op_act_sub
                    & ~ex3_frac_adder_rst_no_shift[56]
                    &  ex3_frac_adder_rst_no_shift[55];

// expn +0 or +1
assign ex3_sub_01xx =  ex3_op_act_sub
                    &  ex3_frac_adder_rst_shift[56];

// expn +0 or +1
assign ex3_sub_001x =  ex3_op_act_sub
                    & ~ex3_frac_adder_rst_shift[56]
                    &  ex3_frac_adder_rst_shift[55];

// expn +0
assign ex3_sub_000x =  ex3_op_act_sub
                    & ~ex3_frac_adder_rst_shift[56]
                    & ~ex3_frac_adder_rst_shift[55];

wire [`DOUBLE_EXPN_WIDTH:0]     ex3_expn_adjust_diff_final;

assign ex3_expn_adjust_diff_final[`DOUBLE_EXPN_WIDTH:0] = ex3_sub_001x ? ex3_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0] + 12'b1 //expn_adjust-1
                                                                       : ex3_expn_adjust_diff[`DOUBLE_EXPN_WIDTH:0]; //expn_adjust

wire                            ex3_rm_rne;
wire                            ex3_rm_rtz;
wire                            ex3_rm_rdn;
wire                            ex3_rm_rup;
wire                            ex3_rm_rmm;

assign ex3_rm_rne = (ex3_rm[2:0] == 3'b000);
assign ex3_rm_rtz = (ex3_rm[2:0] == 3'b001);
assign ex3_rm_rdn = (ex3_rm[2:0] == 3'b010);
assign ex3_rm_rup = (ex3_rm[2:0] == 3'b011);
assign ex3_rm_rmm = (ex3_rm[2:0] == 3'b100);

wire                            ex3_round_g;
wire                            ex3_round_r;
wire                            ex3_round_s;

assign ex3_round_g = ex3_frac_adder_rst_shift[2];
assign ex3_round_r = ex3_frac_adder_rst_shift[1];
assign ex3_round_s = ex3_frac_adder_rst_shift[0];

wire [53:0]                     ex3_frac_adder_rst_after_adjust;

// total 54-bits = overflow:1 + xfrac:53
assign ex3_frac_adder_rst_after_adjust[53:0] = ex3_sub_001x ? {1'b0, ex3_frac_adder_rst_shift[55:3]}
                                                            : {1'b0, ex3_frac_adder_rst_shift[56:4]};

wire                            ex3_rm_rne_add_1;
wire                            ex3_rm_rdn_add_1;
wire                            ex3_rm_rup_add_1;
wire                            ex3_rm_rmm_add_1;
wire                            ex3_rm_add_1;

assign ex3_rm_rne_add_1 = ex3_rm_rne
                        & ex3_round_g;

assign ex3_rm_rdn_add_1 = ex3_rm_rdn
                        & ex3_op_act_sub //-
                        & (~ex3_round_g | ~ex3_round_r | ~ex3_round_s);

assign ex3_rm_rup_add_1 = ex3_rm_rup
                        & ~ex3_op_act_sub //+
                        & (~ex3_round_g | ~ex3_round_r | ~ex3_round_s);

assign ex3_rm_rmm_add_1 = ex3_rm_rmm
                        & ex3_round_g;

assign ex3_rm_add_1 = ex3_rm_rne_add_1
                    | ex3_rm_rdn_add_1
                    | ex3_rm_rup_add_1
                    | ex3_rm_rmm_add_1;

wire [53:0]                     ex3_xfrac_rst_raw_add;
wire [53:0]                     ex3_xfrac_rst_raw_sub;

// total 54-bits = overflow:1 + xfrac:53
assign ex3_xfrac_rst_raw_add[53:0] = ex3_frac_adder_rst_no_shift[56:3];
assign ex3_xfrac_rst_raw_sub[53:0] = ex3_frac_adder_rst_after_adjust[53:0];

wire [53:0]                     ex3_xfrac_rst_sub_add_1;
wire [53:0]                     ex3_xfrac_rst_sub;

wire [53:0]                     ex3_xfrac_rst_add_add_1;
wire [53:0]                     ex3_xfrac_rst_add;

assign ex3_xfrac_rst_sub_add_1[53:0] = ex3_xfrac_rst_raw_sub[53:0] + 54'b1;

assign ex3_xfrac_rst_sub[53:0] = ex3_rm_add_1 ? ex3_xfrac_rst_sub_add_1[53:0]
                                              : ex3_xfrac_rst_raw_sub[53:0];

assign ex3_xfrac_rst_add_add_1[53:0] = ex3_xfrac_rst_raw_add[53:0] + 54'b1;

assign ex3_xfrac_rst_add[53:0] = ex3_rm_add_1 ? ex3_xfrac_rst_add_add_1[53:0]
                                              : ex3_xfrac_rst_raw_add[53:0];

wire [53:0]                     ex3_xfrac_after_adjust;

// after round
assign ex3_xfrac_after_adjust[53:0] = ex3_op_act_sub ? ex3_xfrac_rst_sub[53:0]
                                                     : ex3_xfrac_rst_add[53:0];

wire [11:0]                     ex3_expn_rst_sub_add_1;
wire [11:0]                     ex3_expn_rst_sub_add_0;
wire [11:0]                     ex3_expn_rst_sub;

wire [11:0]                     ex3_expn_rst_add;

assign ex3_expn_rst_sub_add_1[11:0] =  ex3_expn[11:0]
                                    + (~ex3_expn_adjust_diff_final[11:0] + 12'b10); //-|expn_adjust|+1

assign ex3_expn_rst_sub_add_0[11:0] = ex3_expn[11:0]
                                    + (~ex3_expn_adjust_diff_final[11:0] + 12'b1); //-|expn_adjust|

assign ex3_expn_rst_sub[11:0] = ex3_xfrac_after_adjust[53] ? ex3_expn_rst_sub_add_1[11:0]
                                                           : ex3_expn_rst_sub_add_0[11:0];

assign ex3_expn_rst_add[11:0] = ex3_xfrac_after_adjust[53] ? ex3_expn[11:0] + 12'b1
                                                           : ex3_expn[11:0];

wire [11:0]                     ex3_expn_final;
wire [53:0]                     ex3_xfrac_final;

assign ex3_expn_final[11:0] = ex3_op_act_sub ? ex3_expn_rst_sub[11:0]
                                             : ex3_expn_rst_add[11:0];

assign ex3_xfrac_final[53:0] = ex3_xfrac_after_adjust[53] ? {1'b0, ex3_xfrac_after_adjust[53:1]}
                                                          :        ex3_xfrac_after_adjust[53:0];

wire [`FLOAT_WIDTH-1:0]         ex3_rst;

pa_fpu_data_pack u_pa_fpu_data_pack (
    .single_i                   (ex3_single_fmt),
    .src_sign_i                 (ex3_op_act_sign),
    .src_expn_i                 (ex3_expn_final),
    .src_xfrac_i                (ex3_xfrac_final[52:0]),
    .rst_o                      (ex3_rst)
);

reg [`FLOAT_WIDTH-1:0]          ex3_fresult_out;
reg                             ex3_fresult_vld_out;

always @ (posedge ex3_clk_i) begin
    if (ex3_pipedown_i & ~ex3_cancel_i & ex3_op_vld) begin
        ex3_fresult_out[`FLOAT_WIDTH-1:0] <= ex3_rst[`FLOAT_WIDTH-1:0];
    end
    else begin
        ex3_fresult_out[`FLOAT_WIDTH-1:0] <= 64'b0;
    end
end

always @ (posedge ex3_clk_i) begin
    if (~rst_n_i) begin
        ex3_fresult_vld_out <= 1'b0;
    end
    else if (ex3_pipedown_i & ~ex3_cancel_i & ex3_op_vld) begin
        ex3_fresult_vld_out <= 1'b1;
    end
    else begin
        ex3_fresult_vld_out <= 1'b0;
    end
end

assign fresult_o[`FLOAT_WIDTH-1:0] = ex3_fresult_out[`FLOAT_WIDTH-1:0];
assign fresult_vld_o = ex3_fresult_vld_out;

endmodule